# -*- mode: python -*-
###
#
# (c) 2000-2007 Wijnand Modderman <wijnand at tehmaze dot com>
# All rights reserved. See docs/LICENSE
#
###

__revision__ = '$Hg: define.py,v 0f20732e7e4b 2007/01/21 15:36:53 +0100 $'

# system imports
from httplib import HTTPConnection
import copy
import re
import time
import urllib

# DDB imports
from ddb.lang import _
from ddb.db import DB, sql
from ddb.tools import normalize, stripHtml, syntax
from ddb.settings import DB_REENCODE_TO
from ddb.threads import threaded
from ddb.register import register
from ddb.thirdparty import google

# Twisted imports
from twisted.web import client

# DDB exports
EXPORT=['Define']

class _defineDb(DB):
    driver = 'disk'
    layout = {
        'define': [
            sql.Column('id',       sql.Integer,  primary_key=True),
            sql.Column('term',     sql.VARCHAR(length=255)),
            sql.Column('value',    sql.VARCHAR(length=255)),
            # UNIX timestamp when the definition was set
            sql.Column('set_at',   sql.Integer),
            # Identity of the definer
            sql.Column('set_by',   sql.VARCHAR(length=255)),
            # Transport identifier where the term was defined
            sql.Column('set_on',   sql.VARCHAR(length=255)),
            sql.Column('revision', sql.Integer)
        ]
    }

# shared plugin register for both static and dynamic define db
sharedreg = register.init('modules.define')

class Define:
    """Various definition lookup tools."""

    ALIAS = {
        'wp':   'wikipedia',
        'wiki': 'wikipedia',
        'def':  'whatis'
        }

    _re_wikipedia_title = re.compile('<a href="[^"]+" title="([^"]+)">[^<]+</a>')
    _re_question = re.compile('^\?\? (.*)$')

    def __init__(self):
        self.defineDb = _defineDb()
        self._def     = self.defineDb.table('define') # alias
        self.reg      = sharedreg
        self.context  = []

    #
    # Define database
    #

    def _question(self, event):
        """<term>

        query the definition of <term>."""
        result = self._re_question.match(event.data)
        event.args = result.group(1)
        if not event.args:
            event.reply(syntax())
            return
        self.whatis(event)

    def define(self, event):
        """<term> <value>

        define <term> as <value>."""
        if not ' ' in event.args:
            event.reply(syntax())
            return
        term, value = event.args.split(' ', 1)
        term        = term.replace('+', ' ')
        self._def.insert().execute({
            'term':   term,
            'value':  value,
            'set_at': int(time.time()),
            'set_by': event.user.identity,
            'set_on': '%s:%s' % (event.transport.type, event.transport.name),
            })
        event.reply('done')

    def whatis(self, event):
        """<term>

        looks up <term> in the definition database."""
        if not event.args:
            event.reply(syntax())
            return
        result = self._def.select(
            self._def.c.term == event.args
            ).execute()
        if not result:
            return event.reply('\002%s\002: no definition found' % event.args)
        reply = []
        num   = 1
        for row in result:
            reply.append('%d) %s' % (num, row['value']))
            num += 1
        event.reply('\002%s\002: %s' % (event.args, ' '.join(reply)))

    # just like !cc, but now for whatis lookups
    def ccwhatis(self, event):
        """[<prefix>]

        Shows or sets the prefix for whatis lookups."""
        if not event.args:
            prefix = event.register.get('prefix', *event.context)
            if not prefix:
                prefix = '??'
            else:
                prefix = prefix[0]
            event.reply(_('The current whatis lookup prefix is "%s"' % prefix))
        else:
            prefix = event.args
            event.register.set('prefix', prefix, *event.context)
            event.reply(_('The current whatis lookup prefix is set to "%s"' % prefix))

    #
    # Google definitions
    #

    @threaded
    def google(self, event):
        """<[query]>

        Search google for <[query]>."""
        if not event.args:
            return event.reply(syntax())
        googlekey = self.reg.get('googlekey', *self.context)
        if not googlekey:
            return event.reply('The admin has set no google API key, please tell the admin to set %s.googlekey' % self.reg.name)
        google.LICENSE_KEY = googlekey[0]
        try:
            data = google.doGoogleSearch(event.args, outputencoding = 'ISO-8859-1')
        except Exception, e:
            return event.commands._handleException(event, e)
        reply = []
        for result in data.results:
            result.title = result.title.encode('ISO-8859-1','replace').replace('<b>','\x02').replace('</b>','\x02')
            result.URL   = result.URL.encode('ISO-8859-1','replace')
            reply.append('%d: %s @ %s' % (len(reply) + 1, result.title, result.URL))
        event.reply('Results for "%s" (~%s hits): %s' % (event.args, str(data.meta.estimatedTotalResultsCount), ', '.join(reply)))

    #
    # Wikipedia definitions
    #

    @threaded
    def wikipedia(self, event):
        """[--lang <lang>] <term>

        looks up <term> on http://wikipedia.org."""
        if not event.args:
            event.reply(syntax())
            return
        lang  = 'en'
        term  = event.args.replace(' ', '_')
        # Make sure the first letter is capitalized, without destroying the
        # rest of the capitals in the definition
        if ord(term[0]) not in range(ord('A'), ord('Z')+1):
            term = chr(ord(term[0])-32) + term[1:]
        host = '%s.wikipedia.org' % lang
        page = '/wiki/%s' % term
        client.getPage('http://%s%s' % (host, page)).addCallback(
            self._wikipedia_result, event.args, page, event).addErrback(
            self.__http_error, event)

    def _wikipedia_result(self, html, args, page, event):
        if len(html):
            if 'While trying to retrieve the URL' in html:
                event.reply('error fetching %s: %s' % (page, 'invalid URL'))
                return
            refers = False
            result = None
            for line in html.split('\n'):
                line = line.strip()
                if '<p>' in line and not result:
                    line = line.replace('<b>', '\002').replace('</b>', '\002')
                    result = normalize(stripHtml(line))
                    if 'may refer to:' in result:
                        refers = True
                    else:
                        event.reply(result)
                        return
                elif refers and '</ul>' in line:
                    refers = False
                    result = result.replace('may refer to:', 'refers to many articles, be more specific.')
                    event.reply(result)
                    return
                elif refers and '<a href="/wiki' in line:
                    for match in self._re_wikipedia_title.finditer(line):
                        org   = line[match.start():match.end()]
                        line  = line.replace(org, '/\002%s\002/' % match.group())
                    line = line.replace('<b>', '\002').replace('</b>', '\002')
                    result += ' %s' % stripHtml(line)
            event.reply('\002%s\002: %s' % (event.args, 'not found'))

    def __http_error(self, error, event):
        event.reply('error fetching webpage: %s' % error.getErrorMessage())

#
# Handle ?? <term> on message commands
#

class DefineMessage:
    def _onMessage(self, event):
        prefix = event.register.get('prefix', *event.context)
        if not prefix:
            prefix = '??'
        else:
            prefix = prefix[0] # because the register returns lists always
        print "Define._onMessage", event, event.message
        if event.message.startswith('%s ' % prefix):
            newEvent = copy.copy(event) # deepcopy? hmm, no
            newEvent.args     = event.message[3:].strip()
            newEvent.command  = 'whatis'
            newEvent.rawevent = event
            newEvent.type     = 'command'
            newEvent.transport.modules.hook(newEvent)

_defineMessage = DefineMessage()
bind = (
    ['message', _defineMessage._onMessage, 99],
    )

# vim:ts=4:sw=4:et:
